<!DOCTYPE html>
<html lang="en">
  
  <head>
    <meta charset="UTF-8">
    <title>Custom Bigraph Layout</title>
  </head>
  
  <body>
    <div id="description">自定义布局二分图，根据二分图中每个部分节点与另一部分的连接关系，对节点进行排序以减少边的交错</div>
    <div id="mountNode"></div>
    <script src="../build/g6.js"></script>
    <script>
      const data = {
        "nodes": [{
          "id": "0",
          "label": "A",
          "cluster": "part1"
        },
        {
          "id": "1",
          "label": "B",
          "cluster": "part1"
        },
        {
          "id": "2",
          "label": "C",
          "cluster": "part1"
        },
        {
          "id": "3",
          "label": "D",
          "cluster": "part1"
        },
        {
          "id": "4",
          "label": "E",
          "cluster": "part1"
        },
        {
          "id": "5",
          "label": "F",
          "cluster": "part1"
        },
        {
          "id": "6",
          "label": "a",
          "cluster": "part2"
        },
        {
          "id": "7",
          "label": "b",
          "cluster": "part2"
        },
        {
          "id": "8",
          "label": "c",
          "cluster": "part2"
        },
        {
          "id": "9",
          "label": "d",
          "cluster": "part2"
        }],
        "edges": [{
          "source": "0",
          "target": "6"
        },
        {
          "source": "0",
          "target": "7"
        },
        {
          "source": "0",
          "target": "9"
        },
        {
          "source": "1",
          "target": "6"
        },
        {
          "source": "1",
          "target": "9"
        },
        {
          "source": "1",
          "target": "7"
        },
        {
          "source": "2",
          "target": "8"
        },
        {
          "source": "2",
          "target": "9"
        },
        {
          "source": "2",
          "target": "6"
        },
        {
          "source": "3",
          "target": "8"
        },
        {
          "source": "4",
          "target": "6"
        },
        {
          "source": "4",
          "target": "7"
        },
        {
          "source": "5",
          "target": "9"
        }]
      };

      G6.registerLayout('bigraph-layout', {
        getDefaultCfg() {
          return {
            center: [ 0, 0 ],
            biSep: 100, 
            nodeSep: 20,
            direction: 'horizontal',
            nodeSize: 20
          };
        },
        execute() {
          const self = this;
          const center = self.center;
          const biSep = self.biSep;
          const nodeSep = self.nodeSep;
          const nodeSize = self.nodeSize;
          let part1Pos = 0, part2Pos = 0;
          if (self.direction === 'horizontal') {
            part1Pos = center[0] - biSep / 2;
            part2Pos = center[0] + biSep / 2;
          }
          const nodes = self.nodes;
          const edges = self.edges;
          const part1Nodes = [];
          const part2Nodes = [];
          const part1NodeMap = new Map();
          const part2NodeMap = new Map();
          // separate the nodes and init the positions
          nodes.forEach((node, i) => {
            if (node.cluster === 'part1') {
              part1Nodes.push(node);
              part1NodeMap.set(node.id, i);
            } else {
              part2Nodes.push(node);
              part2NodeMap.set(node.id, i);
            }
          });

          // order the part1 node
          part1Nodes.forEach((p1n => {
            let index = 0;
            let adjCount = 0;
            edges.forEach(edge => {
              const sourceId = edge.source;
              const targetId = edge.target;
              if (sourceId === p1n.id) {
                index += part2NodeMap.get(targetId);
                adjCount ++;
              } else if (targetId === p1n.id) {
                index += part2NodeMap.get(sourceId);
                adjCount ++;
              }
            });
            index /= adjCount;
            p1n.index = index;
          }));
          part1Nodes.sort(function(a, b){
            return a.index - b.index
          })
          part2Nodes.forEach((p2n => {
            let index = 0;
            let adjCount = 0;
            edges.forEach(edge => {
              const sourceId = edge.source;
              const targetId = edge.target;
              if (sourceId === p2n.id) {
                index += part1NodeMap.get(targetId);
                adjCount ++;
              } else if (targetId === p2n.id) {
                index += part1NodeMap.get(sourceId);
                adjCount ++;
              }
            });
            index /= adjCount;
            p2n.index = index;
          }));
          part2Nodes.sort(function(a, b){
            return a.index - b.index
          })

          // place the nodes
          const hLength = part1Nodes.length > part2Nodes.length ? part1Nodes.length : part2Nodes.length;
          const height = hLength * (nodeSep + nodeSize);
          let begin = center[1] - height / 2;
          if (self.direction === 'vertical') {
            begin = center[0] - height / 2;
          }
          part1Nodes.forEach((p1n, i) => {
            if (self.direction === 'horizontal') {
              p1n.x = part1Pos;
              p1n.y = begin + i * (nodeSep + nodeSize);
            } else {
              p1n.x = begin + i * (nodeSep + nodeSize);
              p1n.y = part1Pos;
            }
          });
          part2Nodes.forEach((p2n, i) => {
            if (self.direction === 'horizontal') {
              p2n.x = part2Pos;
              p2n.y = begin + i * (nodeSep + nodeSize);
            } else {
              p2n.x = begin + i * (nodeSep + nodeSize);
              p2n.y = part2Pos;
            }
          });
        }
      }, 'mds');

      const graph = new G6.Graph({
        container: 'mountNode',
        width: 800,
        height: 600,
        layout: {
          type: 'bigraph-layout',
          biSep: 300,
          nodeSep: 50,
          nodeSize: 20
        },
        animate: true,
        defaultNode: {
          size: [ 20, 20 ],
          color: 'steelblue',
          style: {
            lineWidth: 2,
            fill: '#fff'
          },
        },
        defaultEdge: {
          size: 1,
          color: '#e2e2e2'
        },
        nodeStateStyles: {
          selected: {
            fill: 'steelblue'
          }
        }
      });
      graph.data(data);
      graph.render();
    </script>
  </body>

</html>